#include "wx/wx.h"
#include "wx/aboutdlg.h"
#include "wx/statline.h"
#include "wx/generic/aboutdlgg.h"
#include "wx/dirdlg.h"
#include "wx/stdpaths.h"
#include "wx/artprov.h"

#include "soci.h"
#include "soci-sqlite3.h"
#include <sqlite3.h>

#include "mediahouseconfig.h"
#include "MediaHouseApp.h"

#include "MediaHouseMainFrm.h"

#include "mhdefs.h"

#include "MediaUpdater.h"

class CMHTreeItemData: public wxTreeItemData {
public:
	CMHTreeItemData(const wxString & data) :
		m_Data(data) {

	}

	virtual ~CMHTreeItemData() {

	}

	bool CompareText(const wxString & data) {
		return data == m_Data;
	}
public:
	wxString m_Data;
};
IMPLEMENT_CLASS(CMediaHouseMainFrm, wxFrame)
BEGIN_EVENT_TABLE(CMediaHouseMainFrm, wxFrame)
EVT_MENU(wxID_EXIT, CMediaHouseMainFrm::OnExit)
	EVT_MENU(mediahouse::MH_MENU_ID_ADD_DIR, CMediaHouseMainFrm::OnAddDir)
	EVT_CLOSE(CMediaHouseMainFrm::OnClose)
	EVT_THREAD(mediahouse::MH_ID_TAG_LIST_CHANGED, CMediaHouseMainFrm::OnThreadUpdate)
	EVT_THREAD(mediahouse::MH_ID_CATEGORY_LIST_CHANGED, CMediaHouseMainFrm::OnThreadUpdate)
	EVT_THREAD(mediahouse::MH_ID_PROGRESS_UPDATED, CMediaHouseMainFrm::OnProgressUpdate)
	EVT_THREAD(mediahouse::MH_ID_PROGRESS_UPDATED_DELTA, CMediaHouseMainFrm::OnProgressUpdate)
	EVT_THREAD(mediahouse::MH_ID_PROGRESS_SET_MAX, CMediaHouseMainFrm::OnProgressUpdate)
END_EVENT_TABLE()

CMediaHouseMainFrm::CMediaHouseMainFrm(wxFrame *frame, wxWindowID id,
		const wxString& title, const wxPoint& pos, const wxSize& size,
		const long type) :
	wxFrame(frame, id, title, pos, size, type),
	m_dlgProgress ((wxProgressDialog *)NULL),
	m_pUpdaterThread(NULL) {
}

CMediaHouseMainFrm::~CMediaHouseMainFrm() {
}

void CMediaHouseMainFrm::Init() {
#if wxUSE_STATUSBAR
	CreateStatusBar(2);
#endif // wxUSE_STATUSBAR
	CreateMenus();

	m_splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition,
			wxDefaultSize, wxSP_3D | wxSP_LIVE_UPDATE | wxCLIP_CHILDREN);

	m_splitter->SetSashGravity(0.25);

	m_left = new wxPanel(m_splitter, wxID_ANY);
	CreateNavigatorPane(m_left);

	m_right = new wxPanel(m_splitter, wxID_ANY);
	CreateContentPane(m_right);

	m_splitter->SetAutoLayout(true);
	m_splitter->SetMinimumPaneSize(1);

	m_splitter->SplitVertically(m_left, m_right, 0);

	LoadNavigatorItems();

	RunMediaUpdater();
}

void CMediaHouseMainFrm::CreateNavigatorPane(wxWindow * parent) {
	wxSizer * topSizer = new wxBoxSizer(wxVERTICAL);

	m_NavigatorCtrl = new wxTreeCtrl(parent, wxID_ANY, wxDefaultPosition,
			wxDefaultSize, wxTR_HIDE_ROOT | wxTR_DEFAULT_STYLE);

	topSizer->Add(m_NavigatorCtrl, 1, wxSHRINK | wxGROW | wxALL | wxALIGN_CENTER);

	parent->SetSizer(topSizer);
	parent->Fit();
}

void CMediaHouseMainFrm::CreateContentPane(wxWindow * parent) {
	wxSizer * topSizer = new wxBoxSizer(wxVERTICAL);

	wxPanel * panel = new wxPanel(parent, wxID_ANY);

	m_ContentSplitter = new wxSplitterWindow(parent, wxID_ANY, wxDefaultPosition,
			wxDefaultSize, wxSP_3D | wxSP_LIVE_UPDATE | wxCLIP_CHILDREN,
			wxT("ContentSplitter"));

	m_ContentSplitter->SetSashGravity(0.2);

	m_top = new wxPanel(m_ContentSplitter, wxID_ANY);

	m_bottom = new wxWindow(m_ContentSplitter, wxID_ANY);

	m_ContentSplitter->SetAutoLayout(true);
	m_ContentSplitter->SetMinimumPaneSize(1);

	m_ContentSplitter->SplitHorizontally(m_top, m_bottom, 0);

	topSizer->Add(m_ContentSplitter, 1, wxGROW | wxALL | wxALIGN_CENTER);

	parent->SetSizer(topSizer);
	parent->Fit();
}

void CMediaHouseMainFrm::LoadNavigatorItems() {
	if (GetThread() && // DoStartALongTask() may have not been called
			GetThread()->IsRunning())
		return;

	if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) {
		wxLogError
			(_("Could not create the worker thread!"));
		return;
	}

	m_WorkerTask = WORKER_DO_LOAD_ITEMS;

	m_dlgProgress = new wxProgressDialog(_("Loading..."),
			_("Wait until the worker finish"), 2, this, wxPD_CAN_ABORT
					| wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME
					| wxPD_REMAINING_TIME);

	m_NavigatorCtrl->DeleteAllItems();
	m_NavigatorCtrl->AddRoot(wxT("Invisible Root"));

	wxGetApp().GetContext()->SetTaskThread(GetThread());
	wxGetApp().GetContext()->CancelTask(false);

	if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
		wxLogError
			(_("Could not run the worker thread!"));
		return;
	}

}

void CMediaHouseMainFrm::CreateMenus() {
	wxMenu * fileMenu = new wxMenu;

	fileMenu->Append(mediahouse::MH_MENU_ID_ADD_FILE, _("Add &File..."),
			_("Add a file to media house"));
	fileMenu->Append(mediahouse::MH_MENU_ID_ADD_DIR, _("Add &Directory..."),
			_("Add a directory to media house"));
	fileMenu->AppendSeparator();

	wxMenuItem * quitMenuItem = new wxMenuItem(fileMenu, wxID_EXIT);
	quitMenuItem->SetAccel(new wxAcceleratorEntry(wxACCEL_CTRL, 'X'));
	quitMenuItem->SetBitmap(wxArtProvider::GetBitmap(wxART_QUIT));

	fileMenu->Append(quitMenuItem);

	wxMenu * helpMenu = new wxMenu;
	wxMenuItem * pItem = helpMenu->Append(mediahouse::MH_MENU_ID_HELP_CONTENT,
			_("&Help Content..."), _("Show Help Content"));
	pItem->SetBitmap(wxArtProvider::GetBitmap(wxART_HELP));

	pItem = helpMenu->Append(wxID_ABOUT);

	wxMenuBar * bar = new wxMenuBar;

	bar->Append(fileMenu, _("&File"));
	bar->Append(helpMenu, _("&Help"));

	SetMenuBar(bar);
}

void CMediaHouseMainFrm::OnExit(wxCommandEvent& WXUNUSED(event) ) {
	Close(true);
}

void CMediaHouseMainFrm::UpdateProgress(wxInt32 progress, const wxString & msg) {
	wxThreadEvent event(wxEVT_COMMAND_THREAD, mediahouse::MH_ID_PROGRESS_UPDATED);

	event.SetInt(progress);

	if (!msg.IsEmpty())
		event.SetString(msg);

	wxQueueEvent(this, event.Clone());
}

wxThread::ExitCode CMediaHouseMainFrm::Entry() {

	switch (m_WorkerTask) {
		case WORKER_DO_LOAD_ITEMS: {
			do {
				if (GetThread()->TestDestroy() || wxGetApp().GetContext()->Cancelled())
					break;

				UpdateProgress(0, _("Loading all tags..."));

				LoadTagsFromDB();

				UpdateProgress(1, _("Loading all categories..."));

				if (GetThread()->TestDestroy() || wxGetApp().GetContext()->Cancelled())
					break;

				LoadCategoriesFromDB();

			} while (false);

			UpdateProgress(-1);
		}
			break;
		case WORKER_DO_SCAN_ITEM: {
			wxGetApp().GetContext()->ScanItem(m_ScanningPath, this);
			UpdateProgress(-1);
			LoadCategoriesFromDB();
		}
			break;
	}

	return (wxThread::ExitCode) 0;
}

void CMediaHouseMainFrm::OnClose(wxCloseEvent&) {
	wxThread::ExitCode rc;

	if (GetThread() && GetThread()->IsRunning()) {
		GetThread()->Delete(&rc);
		GetThread()->Wait();
	}

	if (m_pUpdaterThread) {
		m_pUpdaterThread->Delete(&rc);
		m_pUpdaterThread->Wait();
		delete m_pUpdaterThread;
		m_pUpdaterThread = NULL;
	}

	Destroy();
}

void CMediaHouseMainFrm::OnThreadUpdate(wxThreadEvent& evt) {

	switch (evt.GetId()) {
		case mediahouse::MH_ID_TAG_LIST_CHANGED:
			RefreshTags();
			break;
		case mediahouse::MH_ID_CATEGORY_LIST_CHANGED:
			RefreshCategories();
			break;
		default:
			break;
	}
}

void CMediaHouseMainFrm::RefreshTags() {
	wxCriticalSectionLocker lock(m_DataLocker);

	wxTreeItemId tagsId = FindOrCreateChildItem(m_NavigatorCtrl->GetRootItem(),
			_("Tags"));

	m_NavigatorCtrl->DeleteChildren(tagsId);

	std::vector<std::string>::iterator it;

	for (it = m_Tags.begin(); it != m_Tags.end(); it++) {
		wxTreeItemId tagId = m_NavigatorCtrl->AppendItem(tagsId, wxGetTranslation(
				*it));

		wxTreeItemData * pData = new CMHTreeItemData(*it);

		m_NavigatorCtrl->SetItemData(tagId, pData);
	}

	m_NavigatorCtrl->ExpandAllChildren(tagsId);
}

void CMediaHouseMainFrm::RefreshChildCategories(const wxTreeItemId & parentId,
		wxInt32 parentCategoryId, const wxString & parentPath) {
	wxCriticalSectionLocker lock(m_DataLocker);

	m_NavigatorCtrl->DeleteChildren(parentId);

	std::vector<PCategory>::iterator it;

	for (it = m_Categories.begin(); it != m_Categories.end(); it++) {
		PCategory category = *it;

		if (category->GetParentId() == parentCategoryId) {
			wxString name = category->GetDisplayName();

			if (name.IsNull() || name.IsEmpty()) {
				name = category->GetName();
			}

			wxTreeItemId tagId = m_NavigatorCtrl->AppendItem(parentId,
					wxGetTranslation(name));

			wxString path = parentPath + wxString::Format(wxT("/%d"),
					(int) category.get_id());

			wxTreeItemData * pData = new CMHTreeItemData(path);

			m_NavigatorCtrl->SetItemData(tagId, pData);

			RefreshChildCategories(tagId, category.get_id(), path);
		}
	}

	m_NavigatorCtrl->ExpandAllChildren(parentId);
}

void CMediaHouseMainFrm::RefreshCategories() {
	wxCriticalSectionLocker lock(m_DataLocker);

	wxTreeItemId itemsId = FindOrCreateChildItem(m_NavigatorCtrl->GetRootItem(),
			_("Categories"));

	m_NavigatorCtrl->DeleteChildren(itemsId);

	std::vector<PCategory>::iterator it;

	for (it = m_Categories.begin(); it != m_Categories.end(); it++) {
		PCategory category = *it;

		if (category->GetParentId() == 0) {
			wxString name = category->GetDisplayName();

			if (name.IsNull() || name.IsEmpty()) {
				name = category->GetName();
			}

			wxTreeItemId tagId = m_NavigatorCtrl->AppendItem(itemsId,
					wxGetTranslation(name));

			wxString path = wxString::Format(wxT("/%d"), (int) category.get_id());

			wxTreeItemData * pData = new CMHTreeItemData(path);

			m_NavigatorCtrl->SetItemData(tagId, pData);

			RefreshChildCategories(tagId, category.get_id(), path);
		}
	}

	m_NavigatorCtrl->ExpandAllChildren(itemsId);
}

void CMediaHouseMainFrm::LoadTagsFromDB() {

	try {
		soci::rowset<std::string>
				rs =
						(wxGetApp().GetContext()->GetSqlSession()->prepare
								<< "select distinct item from CMHMedia_CMHTagContainer_m_Tags_items order by item");

		{
			wxCriticalSectionLocker lock(m_DataLocker);
			m_Tags.clear();

			std::copy(rs.begin(), rs.end(), std::back_inserter(m_Tags));
		}
	} catch (soci::soci_error const & e) {
		wxLogError
			(_("LoadTagsFromDB:%s"), e.what());
	}

	wxQueueEvent(this, new wxThreadEvent(wxEVT_COMMAND_THREAD,
			mediahouse::MH_ID_TAG_LIST_CHANGED));
}

void CMediaHouseMainFrm::LoadCategoriesFromDB() {

	try {
		std::vector<hiberlite::bean_ptr<mediahouse::CMHCategory> > categories =
				wxGetApp().GetContext()->GetDatabase()->getAllBeans<
						mediahouse::CMHCategory> ();

		{
			wxCriticalSectionLocker lock(m_DataLocker);
			m_Categories.clear();
			std::copy(categories.begin(), categories.end(), std::back_inserter(
					m_Categories));
		}
	} catch (soci::soci_error const & e) {
		wxLogError
			(_("LoadCategoriesFromDB:%s"), e.what());
	}

	wxQueueEvent(this, new wxThreadEvent(wxEVT_COMMAND_THREAD,
			mediahouse::MH_ID_CATEGORY_LIST_CHANGED));
}

wxTreeItemId CMediaHouseMainFrm::FindOrCreateChildItem(
		const wxTreeItemId & parentId, const wxString & text, bool checkItemData) {
	wxTreeItemIdValue cookie = (wxTreeItemIdValue) this;

	wxTreeItemId childId = m_NavigatorCtrl->GetFirstChild(parentId, cookie);

	while (childId.IsOk()) {
		if (checkItemData) {
			wxTreeItemData * pData = m_NavigatorCtrl->GetItemData(childId);

			if (pData != NULL) {
				CMHTreeItemData * pSData = static_cast<CMHTreeItemData *> (pData);

				if (pSData->CompareText(text))
					return childId;
			}
		} else {
			if (text == m_NavigatorCtrl->GetItemText(childId))
				return childId;
		}

		childId = m_NavigatorCtrl->GetNextChild(parentId, cookie);
	}

	if (checkItemData) {
		childId = m_NavigatorCtrl->AppendItem(parentId, wxGetTranslation(text), -1,
				-1, new CMHTreeItemData(text));
	} else {
		childId = m_NavigatorCtrl->AppendItem(parentId, text);
	}

	return childId;
}

void CMediaHouseMainFrm::OnAddDir(wxCommandEvent& WXUNUSED(event) ) {
	wxStandardPaths stdPath;
	wxDirDialog dlg(this, _("Choose directory"), stdPath.GetDocumentsDir(),
			wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);

	if (wxID_OK == dlg.ShowModal()) {
		ScanItem(dlg.GetPath());
	}
}

void CMediaHouseMainFrm::OnProgressUpdate(wxThreadEvent& event) {
	int n = event.GetInt();

	if (n == -1) {
		m_dlgProgress->Destroy();
		m_dlgProgress = (wxProgressDialog *) NULL;


		// the dialog is aborted because the event came from another thread, so
		// we may need to wake up the main event loop for the dialog to be
		// really closed
		wxWakeUpIdle();
	} else if (m_dlgProgress != NULL) {
		wxString msg = event.GetString();

		if (event.GetId() == mediahouse::MH_ID_PROGRESS_SET_MAX) {

		} else {
			if (event.GetId() == mediahouse::MH_ID_PROGRESS_UPDATED_DELTA) {
				n += m_dlgProgress->GetValue();
			}

			bool ret = msg.IsEmpty() ? m_dlgProgress->Update(n)
					: m_dlgProgress->Update(n, msg);

			if (!ret) {
				wxGetApp().GetContext()->CancelTask(true);
			}
		}
	}
}

void CMediaHouseMainFrm::ScanItem(const wxString & path) {
	if (GetThread() && // DoStartALongTask() may have not been called
			GetThread()->IsRunning())
		return;

	if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR) {
		wxLogError
			(_("Could not create the worker thread!"));
		return;
	}

	m_WorkerTask = WORKER_DO_SCAN_ITEM;
	m_ScanningPath = path;

	m_dlgProgress = new wxProgressDialog(_("Scanning..."),
			_("Wait until the worker finish"), 100, this, wxPD_CAN_ABORT
					| wxPD_APP_MODAL | wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME
					| wxPD_REMAINING_TIME);

	wxGetApp().GetContext()->SetTaskThread(GetThread());
	wxGetApp().GetContext()->CancelTask(false);

	if (GetThread()->Run() != wxTHREAD_NO_ERROR) {
		wxLogError
			(_("Could not run the worker thread!"));
		return;
	}
}

void CMediaHouseMainFrm::RunMediaUpdater() {
	m_pUpdaterThread = new CMediaUpdater(wxGetApp().GetContext(), this);

	if (m_pUpdaterThread->Create() != wxTHREAD_NO_ERROR) {
		wxLogError(_("Can't create the updater thread!"));
		delete m_pUpdaterThread;
		m_pUpdaterThread = NULL;
	} else {
		if (m_pUpdaterThread->Run() != wxTHREAD_NO_ERROR) {
			wxLogError(_("Can't create the thread!"));
			delete m_pUpdaterThread;
			m_pUpdaterThread = NULL;
		}
	}
}
